Fleet changelogs · dev.ecs0.net
rdmsm4x - changelog - RDReceipt receipt inventory 1641 unique files labeled and tagged - claude - TASK-20260926-73 - RDReceipt - 20260926-2311

rdmsm4x - changelog - RDReceipt receipt inventory 1641 unique files labeled and tagged - claude - TASK-20260926-73 - RDReceipt - 20260926-2311

Inventoried and labeled every receipt candidate with RDReceipt's pipeline; fixed a parser date bug; Finder-tagged 4,835 source files in place (no content/mtime change). Notes entry PENDING: headless Background session.

RESULT — TASK-20260926-73: RDReceipt receipt inventory

[2026-09-26 23:10:21 EDT · rdmsm4x] · run 22:57:51 → 23:10 EDT · claude@rdmsm4x (headless, session expenses-receipt-inventory-20260926) · worktree ~/dev/_worktrees/rdreceipt-inventory-20260926, branch expenses/rdreceipt-inventory-20260926

The answer Rich wanted: current expense state

Outputs (all in ~/dev/finance/expenses/inventory/)

file what committed
inventory.csv 1,641 rows, one per unique sha256, with every path (zip members as zip!member) and all fields finance e385911
summary.md counts by kind, category, submitted status and month; gaps; top duplicates; groups; policy flags; low-confidence list finance e385911
report_groups.csv 245 rows in 21 groups R001…R021, ≤ $3,000 each, newest first, split at the 90-day boundary finance e385911
rdreceipt-inventory.sqlite3 RDReceipt's own store (the app's ReceiptDatabase schema): 1,183 OCR'd receipts, plus the additive table inventory_labels (1,641 rows) untracked on purpose: binary, rebuilt in ~4 min, holds raw OCR text

Finder tags kind, YYYY-MM, category and submitted|unsubmitted, plus xattr com.eastcoastscience.rdreceipt.label (JSON), are on 4,835 on-disk files, with 0 errors. The 594 zip members are not tagged, and every one of them also exists as a loose file.

Counts

files read (all 7 roots, including 1,336 extracted zip members) 6,171
unique by sha256 1,641 (1,183 receipt-shaped and OCR'd, 458 other types)
exact duplicate copies 4,530
near-duplicates (same total, ≤ 7 days apart, same merchant or category) 141
ingest failures / hash failures 0 / 0
kind receipt 616 · not-a-receipt 653 · recurring bill 318 · invoice 33 · statement 11 · order confirmation 10
category other 775 · meals 331 · phone/internet 312 · ground/parking/tolls 133 · hotel 39 · airfare 39 · supplies 11 · mileage 1
submitted submitted 181 · likely-submitted 460 · unsubmitted 1,000
low-confidence (< 0.5) receipt-like files 107 (none inside the 90-day window)
policy flags meals need attendees 328 · meals missing an itemized receipt 287 · hotel missing an itemized receipt 21 · over $75 with a confirmation only 3 · phone/internet "wholly work-related" 312
draft cross-check 37/37 of expenses.csv found; 35 match date and amount exactly; 2 (Eataly reservation, Joe's Ginger photo) have no OCR'd total, so Rich's curated amount from the filename is used

Positive control

~/Documents/_receipts/_q3-2024_receipts/24_09_starlink_092524_INV-USA-29503623-76769-94.pdf → kind=recurring bill, date=2024-09-25, merchant=Starlink, total=120.00 USD, tax=0.00, itemized=yes, category=phone/internet, confidence=0.85, unsubmitted, within_90_days=no, group R005. Its Finder tags as read through URLResourceValues.tagNames (the API Finder uses): ["recurring bill", "2024-09", "phone/internet", "unsubmitted"]. All fields match the invoice text (Invoice Date September 25, 2024; Total Charges USD 120.00; Total Tax USD 0.00).

This control caught a real bug. Before the fix it read 2026-09-25. The invoice's year wraps to the next line, and NSDataDetector fills in the current year, which would have put a two-year-old bill inside the 90-day window. The fix is in the shipped parser, commit 9156fe3.

Second control, in the window: bills/starlink/starlink_3764_2026-09.pdf → 2026-09-25, Starlink, $140.00, R001, matching the draft's Batch 1 line exactly. Negative control: rich_arista_W2_2017_TAX.pdf → not-a-receipt with no total. Before the tax-form rule it was a "receipt" dated today for $116,250.

Verification that nothing was modified

Code changes (worktree, committed by explicit path, not merged, not pushed)

commit change
69d1f2a Apps/ReceiptRoo/Tools/inventory/InventoryHarness.swift: a multi-root, persistent-store harness that compiles the production engine (ReceiptScanner/FileHasher/ReceiptReader/ReceiptClassifier/ReceiptFieldParser/ReceiptDatabase), with an exclusion guard and a manifest of every file
9156fe3 Parser fix in Shared/Engine/ReceiptFieldParser.swift: wrapped-year dates, and a year-less date now drops to weak confidence. New fixture invoice-date-year-wrapped-to-next-line: 17/17 pass, and the previous parser fails exactly that case. --reparse mode added: re-parsing 1,183 stored rows changed 29 dates. macOS app ** BUILD SUCCEEDED ** with the fix
84a63c6 tools/inventory/label_inventory.py: labels, the inventory_labels table, the CSVs and summary, and Finder tags

"ReceiptScannerKit" does not exist in this repo. The pipeline is Apps/ReceiptRoo/Shared/Engine, and that is what ran.

Decisions made (DEC-style, reversible)

  1. Submitted = a byte copy in _archive_submitted060424/, Receipts0816/, or Dropbox Receipts (ALL)/2024-08/. The Dropbox folder is a copy of the Aug 16, 2024 Concur package: same filenames, 72 byte-identical files, created 2024-08-21. A near-duplicate of a submitted file inherits submitted. Measured effect: the Dropbox folder alone changed no row, because each of its unique files also has a copy in one of the brief's two folders. Near-duplicate propagation raised submitted from 110 to 181. The Dropbox rule stays in as a guard for future files.
  2. likely-submitted is a CSV status only (dated ≤ 2024-08-16 and filed in an expense-archive folder, or carrying the 06/08/2023 export filename). The Finder tag stays unsubmitted for these. Only proven submissions get the submitted tag, so a missed claim can't hide behind a guess.
  3. Store location: a dedicated rdreceipt-inventory.sqlite3 using the app's schema, not the app's live Application Support store. The SQLCipher vault swap (STATUS item 6, DEC-20260901-02) is still pending, and writing thousands of rows into the plain store now would create data that has to be migrated.
  4. Report grouping follows Rich's draft conventions: AT&T is claimed at a $110 cap, and Verizon and Google Fi are kept out of groups ("borderline, claim $0"). Groups split at the 90-day line so a within-window report never mixes with stale items.
  5. Sanity guards: a date later than the file's own save time is rejected. A total over $10k is dropped and noted in sanity_notes. Scan-timestamp filenames are used only as a 0.3-confidence fallback, flagged "date is the scan date".

Known limits

Verbatim commands

S=/tmp/rdr-inv-20260926
unzip -q -o ~/Documents/_receipts/arista_expenses.zip -x '__MACOSX/*' -d $S/zips/zip-cc2073c6
unzip -q -o ~/Documents/arista_expenses_folder.zip   -x '__MACOSX/*' -d $S/zips/zip-8b809263
cd Apps/ReceiptRoo
swiftc Tools/inventory/InventoryHarness.swift Shared/Engine/ReceiptScanner.swift Shared/Engine/FileHasher.swift \
  Shared/Engine/ReceiptReader.swift Shared/Engine/ReceiptFieldParser.swift Shared/Engine/ReceiptClassifier.swift \
  Shared/Engine/ReceiptDatabase.swift Shared/Models/Receipt.swift -o /tmp/rdreceiptInventory -parse-as-library -lsqlite3
/tmp/rdreceiptInventory ~/dev/finance/expenses/inventory/rdreceipt-inventory.sqlite3 $S/manifest.json \
  ~/Documents/_receipts "$HOME/Library/CloudStorage/Dropbox-richh/Receipts (ALL)" $S/zips/zip-cc2073c6 $S/zips/zip-8b809263 \
  ~/dev/finance/expenses/receipts ~/dev/finance/expenses/bills ~/dev/finance/expenses/source_files
#  → DONE files=6171 ingested=1183 resumed=0 dupes_in_run=4464 failures=0 excluded_entries=0 store_rows=1183  (23:00:31→23:04:36)
/tmp/rdreceiptInventory2 --reparse ~/dev/finance/expenses/inventory/rdreceipt-inventory.sqlite3   # built from 9156fe3
#  → REPARSED rows=1183 dates_changed=29
/tmp/receiptFixtureCheck ../../core/fixtures/rdRECEIPT_field_parsing.json ../../core/fixtures/rdRECEIPT_document_classification.json
#  → fixtures: 17 cases across 2 file(s), all pass
cd ../..
uv run tools/inventory/label_inventory.py --store ~/dev/finance/expenses/inventory/rdreceipt-inventory.sqlite3 \
  --manifest $S/manifest.json --outdir ~/dev/finance/expenses/inventory \
  --zipmap "$S/zips/zip-cc2073c6=<5 identical zips, | separated>" --zipmap "$S/zips/zip-8b809263=$HOME/Documents/arista_expenses_folder.zip" \
  --drafts ~/dev/finance/expenses/expenses.csv --today 2026-09-26 --tag
#  → unique=1641 groups=21 eligible=245 tags {changed/unchanged: 4835, zip-member-skipped: 4008} tag_errors=0 hash_errors=0

Evidence files in this folder: ingest-run.log, manifest.json (paths, sizes and hashes only, with no OCR text), pretag-xattr-backup.json, and untag.py.